package com.shape.sdk;

/**
 * Created by ouzhiyin on 19/01/2018.
 */

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.util.Pair;

import com.shape.sdk.net.BaseResponse;
import com.shape.sdk.net.ShapeContants;
import com.shape.sdk.net.ShapeHttpClient;

import java.io.File;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author ouzhiyin@gmail.com
 */
public class Reporter {

  private final Context context;
  private final ScheduledExecutorService scheduler;
  private final Storage logStorage;

  /**
   * This can be called in main thread.
   */
  Reporter(Context context) {
    this.context = context;
    this.scheduler = Executors.newSingleThreadScheduledExecutor();
    this.logStorage = new Storage(this.context, Reporter.class.getName());
    scheduler.scheduleWithFixedDelay(new Runnable() {
      @Override
      public void run() {
        send();
      }
    }, 0, 1, TimeUnit.MINUTES);
  }

  public void report(Fitness fitness) {
    WriteToStorageTask task = new WriteToStorageTask(fitness);
    scheduler.execute(task);
  }

  private class WriteToStorageTask implements Runnable {

    final Fitness fitness;

    private WriteToStorageTask(final Fitness fitness) {
      this.fitness = fitness;
    }

    @Override
    public void run() {
      logStorage.addResult(fitness.getResultReportJson());
      send();
    }
  }


  private void send() {

//    GZIPOutputStream gzipOutputStream = null;
//    ByteArrayOutputStream outputStream = null;
    int outputId = -1;
    boolean success = false;
    try {
//      outputStream = new ByteArrayOutputStream();
//      gzipOutputStream = new GZIPOutputStream(outputStream, BUFFER_SIZE);
      Pair<Integer, String> result = logStorage.getTailResult();
      if (result == null) {
        return;
      }
//      gzipOutputStream.finish();
      outputId = result.first;
      ShapeHttpClient<BaseResponse> httpClient = new ShapeHttpClient<>(BaseResponse.class);
      BaseResponse baseResponse = httpClient.requestPost(ShapeContants.POST_COURSE_RESULT,
          result.second);

      if (baseResponse != null && baseResponse.getErrorCode() == 0) {
        success = true;
      }
    } catch (OutOfMemoryError e) {
      e.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (outputId > 0) {
        if (success) {
          logStorage.onOutputSuccess(outputId);
        } else {
          logStorage.onOutputFail(outputId);
        }
      }
    }
  }

  public class Storage {

    private static final long STORAGE_FILE_MAX_SIZE = 1024 * 1024 * 2; // 2M
    private SQLiteOpenHelper helper;
    static final String DATABASE_NAME_SUFFIX = "-result.db";
    private static final int DATABASE_VERSION = 1;
    static final String TABLE_NAME = "result_data";

    public static final String RESULT_CONTENT = "result_content";

    public Storage(Context context, String profileName) {
      helper = new SQLiteOpenHelper(context, profileName + DATABASE_NAME_SUFFIX, null, DATABASE_VERSION) {

        @Override
        public void onCreate(SQLiteDatabase db) {
          db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
              + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
              + RESULT_CONTENT + " BLOB" + ");");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int i, int i1) {
          db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
          onCreate(db);
        }

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
          db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
          onCreate(db);
        }
      };
    }

    public Pair<Integer, String> getTailResult() {
      Cursor cursor = getResult();
      if (cursor == null) {
        return null;
      }

      try {

        while (cursor.moveToNext()) {
          byte[] blob =
              cursor.getBlob(cursor.getColumnIndex(RESULT_CONTENT));
          return Pair.create(cursor.getInt(cursor.getColumnIndex(BaseColumns._ID)),
              new String(blob));
        }
      } catch (Throwable e) {
        e.printStackTrace();
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }
      return null;
    }

    public void onOutputSuccess(int resultId) {
      removeById(resultId);
    }

    public void onOutputFail(int resultId) {
      // do nothing
    }

    private boolean checkStorageFileLimit() {
      File file = new File(getDBPath());
      if (file.length() >= STORAGE_FILE_MAX_SIZE) {
        removeTop();
      }
      return true;
    }


    public boolean addResult(String result) {
      if (!checkStorageFileLimit()) {
        return false;
      }
      ContentValues values = new ContentValues();
      values.put(RESULT_CONTENT, result.getBytes());
      return helper.getWritableDatabase().insert(TABLE_NAME, null, values) > 0;
    }

    public boolean removeTop() {
      try {
        helper.getWritableDatabase()
            .execSQL(
                "DELETE FROM " + TABLE_NAME + " WHERE " + BaseColumns._ID
                    + " IN ( SELECT " + BaseColumns._ID
                    + " FROM " + TABLE_NAME + " LIMIT 1)"
            );
        return true;
      } catch (SQLException e) {
        e.printStackTrace();
        return false;
      }
    }

    public boolean removeById(int resultId) {
      try {
        helper.getWritableDatabase()
            .execSQL(
                "DELETE FROM " + TABLE_NAME + " WHERE " + BaseColumns._ID
                    + "=" + resultId
            );
        return true;
      } catch (SQLException e) {
        e.printStackTrace();
        return false;
      }
    }

    public Cursor getResult() {
      // after send success, log will delete from top, so here must order by _id
      return helper.getReadableDatabase().query(TABLE_NAME,
          new String[]{BaseColumns._ID, RESULT_CONTENT}, null, null, null, null,
          BaseColumns._ID + " DESC");
    }

    public String getDBPath() {
      return helper.getReadableDatabase().getPath();
    }
  }
}

